# coding=utf8
import copy
from typing import Iterable

from pyjvm.runtime.metadata import Class, Field
from pyjvm.utils import java_default_of, ClassNameUtil


class JObject(object):

    def __init__(self, klass: Class):
        self.klass: Class = klass
        self.is_array = False if klass is None else klass.name.startswith('[')
        self.data = None  # object type: {id(field): value}, array type : []
        self.extra = None

    @staticmethod
    def new(klass: Class, init_field=True):
        self = JObject(klass)
        self.klass = klass
        if init_field:
            self.data = {}
            JObject.__init_fields(self)
        return self

    @staticmethod
    def new_array(arr_class: Class, length: int):
        self = JObject(arr_class)
        self.klass = arr_class
        self.data = [java_default_of(ClassNameUtil.get_component_class_name(arr_class.name))] * length
        return self

    @staticmethod
    def new_from_data(arr_class: Class, data):
        self = JObject(arr_class)
        self.klass = arr_class
        self.data = data
        return self

    def value_of(self, field: Field):
        return self.data[id(field)]

    def put(self, field: Field, val):
        self.data[id(field)] = val

    def instance_of(self, other: Class) -> bool:
        return other.accessible_from(self.klass)

    def get_field_value(self, name: str, descriptor: str):
        field = self.klass.get_instance_field(name, descriptor)
        return self.data[id(field)]

    def put_field_value(self, name: str, descriptor: str, obj):
        field = self.klass.get_instance_field(name, descriptor)
        self.data[id(field)] = obj

    def clone(self):
        obj = JObject(self.klass)
        obj.data = copy.copy(self.data)
        return obj

    @staticmethod
    def __init_fields(self):
        klass = self.klass
        while klass:
            self.data.update({
                id(field): java_default_of(field.descriptor)
                for field in klass.fields})
            klass = klass.super_class

    def __str__(self):
        return 'JObject{class:%s,is_array:%s}' % (self.klass.name, self.is_array)


class Heap(object):

    @staticmethod
    def new_object(klass: Class) -> JObject:
        return JObject.new(klass)

    @staticmethod
    def new_array(arr_class: Class, length: int) -> JObject:
        return JObject.new_array(arr_class, length)

    @staticmethod
    def new_from_data(arr_class: Class, data) -> JObject:
        return JObject.new_from_data(arr_class, data)

    @staticmethod
    def new_array_by_item_class(item_class: Class, length: int) -> JObject:
        return JObject.new_from_data(item_class.get_component_array_class(), length)


def py_classes_to_j_array_obj(loader, classes: Iterable[Class]) -> JObject:
    ret_class = loader.get_component_array_class(loader.load_class('java/lang/Class'))
    ret_arr = Heap.new_from_data(ret_class, [klass.j_class for klass in classes])
    return ret_arr


def py_bytes_to_j_bytes_obj(loader, py_bytes: bytes) -> JObject:
    return Heap.new_from_data(loader.load_class('[B'), list(py_bytes))


__all__ = ['JObject', 'Heap', 'py_classes_to_j_array_obj', 'py_bytes_to_j_bytes_obj']
